#include "RX3DRender/ShadowNodes/RX3DMeshShadowNode.h"
#include "RX3DRender/ShadowNodes/RX3DCameraShadowNode.h"
#include "RX3DRender/ShadowNodes/RX3DRendererShadowNode.h"
#include "RX3DRender/ShadowNodes/RX3DMaterialShadowNode.h"
#include "RX3DRender/ShadowNodes/RX3DMeshAssetShadowNode.h"
#include "RX3DRender/ShadowNodes/RX3DRenderActorShadowNode.h"
#include "RX3DRender/ShadowNodes/RX3DShaderProgramShadowNode.h"
#include "RX3DRender/ShadowNodes/RX3DTextureShadowNode.h"


#include "RX3DPainterData.h"

struct PainterDataMeta
{
	std::function<bool(RXShadowNode*)> mark;
	std::function<RX3DRenderData*()> create;
};

struct PainterDataMetaSys
{
	std::vector<PainterDataMeta> datas;
};

template<typename ShadowType, typename DataType>
void RegisterData(PainterDataMetaSys& meta) {
	meta.datas.push_back({ [](RXShadowNode* node) {
	return dynamic_cast<ShadowType*>(node) != NULL;
	},[]() {
	return new DataType();
	} });
}

static void InitMeta(PainterDataMetaSys& meta) {
	RegisterData<RX3DMeshShadowNode,MeshData>(meta);
	RegisterData<RX3DCameraShadowNode, CameraData>(meta);
	RegisterData<RX3DRendererShadowNode, RenderData>(meta);
	RegisterData<RX3DMaterialShadowNode, MaterialData>(meta);
	RegisterData<RX3DMeshAssetShadowNode, MeshAssetData>(meta);
	RegisterData<RX3DRenderActorShadowNode, RenderActorData>(meta);
	RegisterData<RX3DShaderProgramShadowNode, ShaderProgramData>(meta);
	RegisterData<RX3DTextureShadowNode, TextureData>(meta);
}

void RX3DRenderData::MarkDestory()
{
	isDestory = true;
}

void RX3DRenderData::HandleUpdate()
{
	if (isDirty)
	{
		handleUpdateImp();
		isDirty = false;
	}
}

void RX3DRenderData::CopyData(RXShadowNode * shadowNode)
{
	CopyDataImp(shadowNode);
	isDirty = true;
}

RX3DRenderDataPtr RX3DRenderData::CreateData(RXShadowNode * shadowNode)
{
	static PainterDataMetaSys meta = []() {
		PainterDataMetaSys	meta;
		InitMeta(meta);
		return meta;
	}();

	for (const auto& i : meta.datas)
	{
		if (i.mark(shadowNode))
		{
			auto ret = std::shared_ptr<RX3DRenderData>(i.create());
			ret->id = shadowNode->GetNodeId();
			return ret;
		}
	}
	return RX3DRenderDataPtr();
}

MeshData::~MeshData()
{
}

void MeshData::handleUpdateImp()
{

}

void MeshData::CopyDataImp(RXShadowNode * shadowNode)
{
	auto node = static_cast<RX3DMeshShadowNode*>(shadowNode);

	m_MeshAsset = node->m_MeshAsset;
	m_worldMartix = node->m_WorldMatrix;
}

CameraData::~CameraData()
{
}

void CameraData::handleUpdateImp()
{
}

void CameraData::CopyDataImp(RXShadowNode * shadowNode)
{
	auto node = static_cast<RX3DCameraShadowNode*>(shadowNode);
	m_worldMartix = node->m_WorldMatrix;
	m_worldMartixInverted = m_worldMartix.inverse();
}

RenderData::~RenderData()
{
}

void RenderData::handleUpdateImp()
{
}

void RenderData::CopyDataImp(RXShadowNode * shadowNode)
{
	auto node = static_cast<RX3DRendererShadowNode*>(shadowNode);

	m_output = node->m_output;
	m_viewPort = node->m_viewPort;
}

MaterialData::~MaterialData()
{
}

void MaterialData::handleUpdateImp()
{
}

void MaterialData::CopyDataImp(RXShadowNode * shadowNode)
{
	auto node = static_cast<RX3DMaterialShadowNode*>(shadowNode);
	m_shaderProgram = node->m_shaderProgram;
	m_texture = node->m_texture;
}

MeshAssetData::~MeshAssetData()
{
}

void MeshAssetData::handleUpdateImp()
{
}

void MeshAssetData::CopyDataImp(RXShadowNode * shadowNode)
{
	auto node = static_cast<RX3DMeshAssetShadowNode*>(shadowNode);

	m_mesh = node->m_mesh;
}

RenderActorData::~RenderActorData()
{
}

void RenderActorData::handleUpdateImp()
{
}

void RenderActorData::CopyDataImp(RXShadowNode * shadowNode)
{
	auto node = static_cast<RX3DRenderActorShadowNode*>(shadowNode);

	m_matrix = node->m_matrix;
	m_meshAsset = node->m_meshAsset;
	m_materialAsset = node->m_materialAsset;
}

ShaderProgramData::~ShaderProgramData()
{
	if (gl_shader_program)
	{
		glDeleteProgram(gl_shader_program);
	}
	if (gl_fShader)
	{
		glDeleteShader(gl_fShader);
	}
	if (gl_vShader)
	{
		glDeleteShader(gl_vShader);
	}
}

bool CreateShader(GLuint & gl_shader,RXShaderBuffer::ShaderType type,const char* data)
{
	if (gl_shader)
	{
		glDeleteShader(gl_shader);
		gl_shader = 0;
	}

	if (strlen(data))
	{
		return false;
	}

	GLenum shaderType = 0;
	if (type == RXShaderBuffer::Fragment)
	{
		shaderType = GL_FRAGMENT_SHADER;
	}
	else if (type == RXShaderBuffer::Vertices)
	{
		shaderType = GL_VERTEX_SHADER;
	}
	else {
		return false;
	}

	gl_shader = glCreateShader(shaderType);
	const char* shaderp = data;
	glShaderSource(gl_shader, 1, &shaderp, NULL);
	glCompileShader(gl_shader);

	return true;
}

void ShaderProgramData::handleUpdateImp()
{
	if (gl_shader_program)
	{
		glDeleteProgram(gl_shader_program);
		gl_shader_program = 0;
	}

	bool f = CreateShader(gl_fShader, RXShaderBuffer::Fragment,shaderBuffer.GetShader(RXShaderBuffer::Fragment).c_str());
	bool v = CreateShader(gl_vShader, RXShaderBuffer::Vertices, shaderBuffer.GetShader(RXShaderBuffer::Vertices).c_str());

	if (!f || !v)
	{
		gl_shader_program = glCreateProgram();
		glAttachShader(gl_shader_program, gl_vShader);
		glAttachShader(gl_shader_program, gl_fShader);
		glLinkProgram(gl_shader_program);
	}
}

void ShaderProgramData::CopyDataImp(RXShadowNode * shadowNode)
{
	auto node = static_cast<RX3DShaderProgramShadowNode*>(shadowNode);

	shaderBuffer = node->m_shader;
}

TextureData::~TextureData()
{
	if (!gl_textures.empty())
	{
		glDeleteTextures(gl_textures.size(), gl_textures.data());
	}
}

inline GLenum ToGLType(RXTexture::TextureWarp warp)
{
	switch (warp)
	{
	case RXTexture::TW_Repeat:
		return GL_REPEAT;
		break;
	case RXTexture::TW_Mirrored_Repeat:
		return GL_MIRRORED_REPEAT;
		break;
	case RXTexture::TW_Clamp_To_Edge:
		return GL_CLAMP_TO_EDGE;
		break;
	case RXTexture::TW_Clamp_ToBorder:
		return GL_CLAMP_TO_BORDER;
		break;
	}

	return 0;
}

inline GLenum ToGLType(RXTexture::Filter filter)
{
	switch (filter)
	{
	case RXTexture::MM_Nearest:
		return GL_NEAREST;
		break;
	case RXTexture::MM_Linear:
		return GL_LINEAR;
		break;
	case RXTexture::MM_Nearest_Nearest:
		return GL_NEAREST_MIPMAP_NEAREST;
		break;
	case RXTexture::MM_Linear_Nearest:
		return GL_LINEAR_MIPMAP_NEAREST;
		break;
	case RXTexture::MM_Nearest_Linear:
		return GL_NEAREST_MIPMAP_LINEAR;
		break;
	case RXTexture::MM_Linear_Linear:
		return GL_LINEAR_MIPMAP_LINEAR;
		break;
	default:
		break;
	}
	return 0;
}

inline GLenum ToGLType(Image::ScalarType type)
{
	switch (type)
	{
	case IUImage::ST_INT8:
		return GL_BYTE;
		break;
	case IUImage::ST_INT16:
		return GL_SHORT;
		break;
	case IUImage::ST_INT32:
		return GL_INT;
		break;
	case IUImage::ST_UINT8:
		return GL_UNSIGNED_BYTE;
		break;
	case IUImage::ST_UINT16:
		return GL_UNSIGNED_SHORT;
		break;
	case IUImage::ST_UINT32:
		return GL_UNSIGNED_INT;
		break;
	case IUImage::ST_FLOAT:
		return GL_FLOAT;
		break;
	case IUImage::ST_DOUBLE:
		return GL_DOUBLE;
		break;
	}

	return 0;
}

inline GLenum GetImageFormate(int tupleSize) {
	switch (tupleSize)
	{
	case 3:
		return GL_RGB;
	case 4:
		return GL_RGBA;
	default:
		break;
	}

	return 0;
}

void TextureData::handleUpdateImp()
{
	if (!gl_textures.empty())
	{
		glDeleteTextures(gl_textures.size(), gl_textures.data());
		gl_textures.clear();
		textureMap.clear();
	}

	std::map<std::string, Variant> textureMap;
	for (const auto & name : texture.GetAttributeNames())
	{
		Variant v = texture.GetAttribute(name.c_str());
		if (v.TypeId() == MetaType::IdFromType<Image>())
		{
			textureMap[name] = v;
		}
	}

	gl_textures.resize(textureMap.size(), 0);
	glGenTextures(gl_textures.size(), gl_textures.data());
	int index = 0;
	for (const auto & i : textureMap)
	{
		textureMap[i.first] = index;
		RXTexture texture = i.second.Value<RXTexture>();
		GLuint gl_texture = gl_textures[index];
		const auto & image = texture.GetImage();

		if (image.IsNull())
		{
			continue;
		}
		glBindTexture(GL_TEXTURE_2D, gl_texture);

		auto size = image.GetSize();

		glTexImage2D(GL_TEXTURE_2D, 0, GetImageFormate(image.GetTupleSize()), size.GetWidth(), size.GetHeight(), 0, GetImageFormate(image.GetTupleSize()), ToGLType(image.GetScalarType()), image.GetData());

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, ToGLType(texture.GetWarpS()));
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, ToGLType(texture.GetWarpT()));
		glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, texture.GetBorderColor().GetData());

		if (texture.GetMinFilter() != RXTexture::Filter::MM_None)
		{
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ToGLType(texture.GetMinFilter()));
		}

		if (texture.GetMagFilter() != RXTexture::Filter::MM_None)
		{
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ToGLType(texture.GetMagFilter()));
		}

		index++;
	}
	glBindTexture(GL_TEXTURE_2D, 0);
}

void TextureData::CopyDataImp(RXShadowNode * shadowNode)
{
	auto node = static_cast<RX3DTextureShadowNode*>(shadowNode);

	texture = node->texture;
}
